package cn.jonay.pdmreader.core.parser;

import cn.jonay.pdmreader.core.meta.*;
import cn.jonay.pdmreader.util.ClassUtil;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;

import java.util.*;

/**
 * 说明
 *
 * @author zhaozhiguo
 * @version 1.0
 * @date 2021/2/8 9:15
 */
public class Parser {

    private Document doc;

    public Parser(Document doc) {
        this.doc = doc;
    }

    public <T extends Entity> T parseEntity(String xpath, Class<T> entityClass) {
        Node model = doc.selectSingleNode(xpath);
        return parseEntity(model, entityClass);
    }

    private String getNodeText(Node node, String path) {
        Node n = node.selectSingleNode(path);
        if (n != null) {
            return n.getText().trim();
        }
        return null;
    }

    private int getNodeInt(Node node, String path) {
        Node n = node.selectSingleNode(path);
        if (n != null) {
            return Integer.parseInt(n.getText());
        }
        return 0;
    }

    private <T extends Entity> T parseEntity(Node node, Class<T> entityClass) {
        T entity = ClassUtil.newInstance(entityClass);
        entity.setId(((Element) node).attributeValue("Id"));
        entity.setName(getNodeText(node, "a:Name"));
        entity.setCode(getNodeText(node, "a:Code"));
        entity.setComment(getNodeText(node, "a:Comment"));
        return entity;
    }

    public List<Table> parseTables() {
        List<Table> tables = new ArrayList<>();
        Node node = doc.selectSingleNode("//c:Children/o:Model");
        List nodes = node.selectNodes(".//c:Tables/o:Table");
        for (Object oTable : nodes) {
            Node tableNode = (Node) oTable;
            Table table = parseEntity(tableNode, Table.class);

            // 添加Columns
            Map<String, Column> columns = parseColumns(tableNode);
            table.setColumns(new ArrayList<>(columns.values()));

            // 添加key
            Map<String, Index> keys = new HashMap<>();
            for (Object key : tableNode.selectNodes("c:Keys/o:Key")) {
                Node keyNode = (Node) key;
                Index pdmKey = parseEntity(keyNode, Index.class);
                for (Object column : keyNode.selectNodes("c:Key.Columns/o:Column")) {
                    String id = ((Element) column).attributeValue("Ref");
                    pdmKey.getColumns().add(columns.get(id).getId());
                }
                keys.put(pdmKey.getId(), pdmKey);
            }

            // 添加PrimaryKey
            Node keyNode = tableNode.selectSingleNode("c:PrimaryKey/o:Key");
            if (null != keyNode) {
                String keyId = ((Element) keyNode).attributeValue("Ref");
                Index keyIndex = keys.get(keyId);
                if (keyIndex != null) {
                    keyIndex.getColumns().forEach(id -> {
                        Column column = columns.get(id);
                        column.setPk(true);
                        column.setNullable(false);
                    });
                }
            }

            // 添加Indexes
            for (Object oIndex : tableNode.selectNodes("c:Indexes/o:Index")) {
                Node indexNode = (Node) oIndex;
                Index index = parseEntity(indexNode, Index.class);
                for (Object column : indexNode.selectNodes(".//c:Column/o:Column")) {
                    String id = ((Element) column).attributeValue("Ref");
                    Column refCol = columns.get(id);
                    if (refCol != null) {
                        index.getColumns().add(refCol.getCode());
                    }
                }
                table.getIndexes().add(index);
            }

            tables.add(table);
        }
        return tables;
    }

    private Map<String, Column> parseColumns(Node tableNode) {
        Map<String, Column> columnMap = new LinkedHashMap<>();
        for (Object oColumn : tableNode.selectNodes(".//c:Columns/o:Column")) {
            Node columnNode = (Node) oColumn;
            if (columnNode == null) {
                break;
            }
            Column pdmColumn = parseEntity(columnNode, Column.class);
            pdmColumn.setDataType(getNodeText(columnNode, "a:DataType"));
            pdmColumn.setLength(getNodeInt(columnNode, "a:Length"));
            pdmColumn.setScale(getNodeInt(columnNode, "a:Precision"));
            pdmColumn.setNullable(0 == getNodeInt(columnNode, "a:Column.Mandatory"));
            pdmColumn.setDefaultValue(getNodeText(columnNode, "a:DefaultValue"));
            columnMap.put(pdmColumn.getId(), pdmColumn);
        }
        return columnMap;
    }

    public List<Reference> parseReferences() {
        List<Reference> referenceList = new ArrayList<>();
        Node node = doc.selectSingleNode("//c:Children/o:Model");
        for (Object oReference : node.selectNodes("c:References/o:Reference")) {
            Node referenceNode = (Node) oReference;
            Reference reference = parseEntity(referenceNode, Reference.class);
            reference.setConstraintName(getNodeText(referenceNode, "ForeignKeyConstraintName"));
            reference.setUpdateConstraint(getNodeInt(referenceNode, "UpdateConstraint"));
            reference.setDeleteConstraint(getNodeInt(referenceNode, "DeleteConstraint"));
            reference.setImplementationType(getNodeText(referenceNode, "ImplementationType"));
            // 添加ParentTable
            String parentTableId = ((Element) referenceNode.selectSingleNode("c:ParentTable/o:Table")).attributeValue("Ref");
            reference.setFromTable(parentTableId);
            // 添加ChildTable
            String childTableId = ((Element) referenceNode.selectSingleNode("c:ChildTable/o:Table")).attributeValue("Ref");
            reference.setToTable(childTableId);
            // 添加Joins
            for (Object oJoin : referenceNode.selectNodes("c:Joins/o:ReferenceJoin")) {
                Node referenceJoinNode = (Node) oJoin;
                Node fromNode = referenceJoinNode.selectSingleNode("c:Object1/o:Column");
                Node toNode = referenceJoinNode.selectSingleNode("c:Object2/o:Column");
                if (fromNode==null || toNode==null) {
                    continue;
                }
                ReferenceJoin referenceJoin = parseEntity(referenceJoinNode, ReferenceJoin.class);
                referenceJoin.setFromColumn(((Element) fromNode).attributeValue("Ref"));
                referenceJoin.setToColumn(((Element) toNode).attributeValue("Ref"));
                reference.getJoins().add(referenceJoin);
            }

            referenceList.add(reference);
        }
        return referenceList;
    }
}
